﻿using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;
using System;
using System.Diagnostics;

namespace BeiKe.PhotoTransfer.Lib
{
    public class RecevieFileInfo {
        public string filename;
        public string md5;
    }
    public class PhotoTransfer
    {
        public PhotoTransfer(string ip, int port)
        {
            setEndPoint(ip, port);
        }
        private float progress = 0.0F;

        public float Progress { get => progress; set => progress = value; }

        public string EndPoint
        {
            get
            {
                return $"{end_point.Address}:{end_point.Port}";
            }
            set
            {
                string[] temp = value.Split(':');
                try
                {
                    end_point.Address = IPAddress.Parse(temp[0]);
                    end_point.Port = int.Parse(temp[1]);
                }
                catch
                {
                    throw new Exception("值无效");
                }
            }
        }

        IPEndPoint end_point;

        public void setEndPoint(string ip, int port)
        {
            end_point = new IPEndPoint(IPAddress.Parse(ip), port);
        }

        TcpListener listener = null;
        TcpClient client = null;

        NetworkStream net_stream = null;

        public async Task<bool> StartServe()
        {

            listener = listener ?? new TcpListener(end_point);

            listener.Start();

            var client = await listener.AcceptTcpClientAsync();

            net_stream = client.GetStream();

            RecevieFileInfo fileinfo = ReceiveFileInfo(client);


            return await ReceiveAsnyc(fileinfo);
        }

        public RecevieFileInfo ReceiveFileInfo(TcpClient client)
        {
            RecevieFileInfo file_info = new RecevieFileInfo();

            Type fileinfo_type = file_info.GetType();

            MemoryStream memstream = new MemoryStream();
            while (true) {
                byte achar = (byte)net_stream.ReadByte();

                memstream.WriteByte(achar);

                if (achar == 0) {
                    break;
                }
            }
            memstream.Seek(0, SeekOrigin.Begin);
            StreamReader reader = new StreamReader(memstream);
            while (true)
            {
                string line = reader.ReadLine();

                if (line.Length == 0)
                {
                    break;
                }

                string[] line_split = line.Split(':');
                string key = line_split[0];
                string value = line_split[1];
                fileinfo_type.GetField(key.ToLower()).SetValue(file_info,value);
                
            }
            reader.Close();
            memstream.Close();
            return file_info;
        }

        public Task<bool> ReceiveAsnyc(RecevieFileInfo fileinfo) {
            return Task.Run(() =>
            {
                return Receive(fileinfo);
            });
        }

        public bool Receive(RecevieFileInfo fileinfo)
        {
            if (end_point == null)
            {
                return false;
            }

            FileStream fileStream = new FileStream(fileinfo.filename, FileMode.Create);

            byte[] buffer = new byte[1024 * 1024];
            int recv_size;
            while (true)
            {
                recv_size = net_stream.Read(buffer, 0, buffer.Length);

                if (recv_size <= 0)
                {
                    break;
                }

                fileStream.Write(buffer, 0, recv_size);
            }

            fileStream.Seek(0, SeekOrigin.Begin);
            string md5 = ComputeHash(fileStream);//await ComputeHash(fileStream);

            net_stream.Close();
            fileStream.Close();
            listener.Stop();

            return md5 == fileinfo.md5;
        }

        public async void SendFileAsync(string filePath)
        {
            await Task.Run(() =>
            {
                SendFile(filePath);
            });
        }

        public void Connect()
        {
            client = new TcpClient();
            client.Connect(end_point);
        }

        public void SendFileInfo(string filePath)
        {
            Connect();
            var net_stream = client.GetStream();

            FileInfo file_info = new FileInfo(filePath);
            FileStream fileStream = new FileStream(file_info.FullName, FileMode.Open);
            string md5 = ComputeHash(fileStream);

            string header
                = $"FILENAME:{file_info.Name}\r\nMD5:{md5}";

            byte[] buffer = Encoding.UTF8.GetBytes(header + "\r\n\r\n\0");
            net_stream.Write(buffer, 0, buffer.Length);
            fileStream.Close();
        }

        public void SendFile(string filePath)
        {

            SendFileInfo(filePath);

            var stream = client.GetStream();

            var total_send_size = 0L;

            var buffer = new byte[1024 * 1024];

            FileInfo file_info = new FileInfo(filePath);
            var fileStream = new FileStream(file_info.FullName, FileMode.Open);

            while (true)
            {

                int read_length = fileStream.Read(buffer, 0, buffer.Length);

                if (read_length <= 0)
                {
                    break;
                };

                stream.Write(buffer, 0, read_length);

                total_send_size += read_length;

                progress = 1.0F * total_send_size / file_info.Length;
            }
            stream.Close();
            fileStream.Close();

        }

        public Task<string> ComputeHashAsync(FileStream fileStream)
        {
            return Task.Run(() =>
            {
                return ComputeHash(fileStream);
            });
        }

        public string ComputeHash(FileStream fileStream)
        {
            MD5 md5 = new MD5CryptoServiceProvider();

            byte[] md5_bytes = md5.ComputeHash(fileStream);

            StringBuilder sb = new StringBuilder();
            foreach (byte _byte in md5_bytes)
            {
                sb.Append(_byte.ToString("x2"));
            }

            return sb.ToString();
        }
    }
}
